Skip to content

feat(rest): implement OAuth2 token auto-refresh for REST catalog#646

Open
lishuxu wants to merge 9 commits into
apache:mainfrom
lishuxu:feature/oauth_2
Open

feat(rest): implement OAuth2 token auto-refresh for REST catalog#646
lishuxu wants to merge 9 commits into
apache:mainfrom
lishuxu:feature/oauth_2

Conversation

@lishuxu
Copy link
Copy Markdown
Contributor

@lishuxu lishuxu commented May 9, 2026

Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.

Comment thread src/iceberg/test/meson.build Outdated
Comment thread src/iceberg/util/transform_util.cc Outdated
Comment thread src/iceberg/util/transform_util.cc Outdated
Comment thread src/iceberg/catalog/rest/auth/auth_session.cc Outdated
Comment thread src/iceberg/catalog/rest/auth/auth_session.cc
{std::string(kAuthorizationHeader), std::string(kBearerPrefix) + token_}};

// Update expiration
if (response.expires_in_secs.has_value()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reset expires_at_ before deriving the new expiry, and skip scheduling when the refreshed token has neither expires_in nor JWT exp.

Copy link
Copy Markdown
Contributor Author

@lishuxu lishuxu May 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Now resetting expires_at_ to epoch before computing the new value.

.client_id = client_id,
.client_secret = client_secret,
.scope = scope,
.keep_refreshed = true,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pass the parsed token-refresh-enabled value into this config instead of forcing refresh on. Users who disable refresh should not get background token requests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Added keep_refreshed parameter to MakeOAuth2, passed from config.keep_refreshed().

shuxu.li added 9 commits May 22, 2026 17:59
    Replace the MakeOAuth2 stub with a full OAuth2AuthSession that
    automatically refreshes tokens before expiration using the
    client_credentials grant.

    Key components:
    - OAuth2AuthSession: manages token lifecycle with shared_mutex for
      concurrent read access and background refresh via scheduler
    - TokenRefreshScheduler: process-global singleton with a single worker
      thread that fires delayed refresh callbacks
    - ExpiresAtMillis: JWT exp claim parser for determining token expiry
      when expires_in is not provided in the token response
    - Base64Decode/Base64UrlDecode added to TransformUtil as public utilities
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that
automatically refreshes tokens before expiration using the
client_credentials grant.

Key components:
- OAuth2AuthSession: manages token lifecycle with shared_mutex for concurrent read access and background refresh via scheduler
- TokenRefreshScheduler: process-global singleton with a single worker thread that fires delayed refresh callbacks
- ExpiresAtMillis: JWT exp claim parser for determining token expiry when expires_in is not provided in the token response
- Base64Decode/Base64UrlDecode added to TransformUtil as public utilities
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
Replace the MakeOAuth2 stub with a full OAuth2AuthSession that automatically refreshes tokens before expiration using the client_credentials grant.
@lishuxu lishuxu force-pushed the feature/oauth_2 branch from ab7d3a9 to 60e8584 Compare May 22, 2026 13:10
};

/// \brief Create an OAuth2 session and optionally schedule refresh.
static std::shared_ptr<OAuth2AuthSession> Create(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static std::shared_ptr<OAuth2AuthSession> Create(
static std::shared_ptr<OAuth2AuthSession> Make(

nit: rename it to Make to be consistent with this repo.

/// (a single HTTP POST to refresh a token), so one thread is sufficient.
///
/// Thread safety: All public methods are thread-safe.
class ICEBERG_REST_EXPORT TokenRefreshScheduler {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HuaHuaY is working on the adding a thread pool abstraction and it would be good to reuse that once available. The main issue of current design is that only a single thread is working on token refresh and a slow request would starve all other tasks.

props.mutable_configs().insert_or_assign(key, value);
}

auto result = FetchToken(client_, *empty_session, props);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java refreshes through token exchange by default and only falls back to client credentials in specific cases. This path always uses client_credentials and ignores token-exchange-enabled, which breaks parity for exchange-based refresh.

config.optional_oauth_params(), client);
}

// If token is provided, use it directly.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java wraps configured access tokens in fromAccessToken() so expiring tokens can be refreshed. Returning a static session here means JWT or token-expires-in-ms based tokens never refresh.

///
/// Handles optional padding ('=').
/// \return Decoded string, or an error if the input contains invalid characters.
static Result<std::string> Base64Decode(std::string_view encoded);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can refactor a separate iceberg/util/base64.h for these Base64XXX functions.

/// Handles optional padding ('='). This variant uses '-' and '_' instead of
/// '+' and '/' per RFC 4648 §5.
/// \return Decoded string, or an error if the input contains invalid characters.
static Result<std::string> Base64UrlDecode(std::string_view encoded);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to add a Base64UrlEncode for parity?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants